package day20180131.selfDefinedClassLoader;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;

/**
 * Created by 14394 on 2018/1/31.
 */

/*为什么我们要自定义类加载器？因为虽然Java中给用户提供了很多类加载器，但是和实际使用比起来，功能还是匮乏。举一个例子来说吧，主流的Java Web服务器，比如Tomcat，
都实现了自定义的类加载器（一般都不止一个）。因为一个功能健全的Web服务器，要解决如下几个问题：

1、部署在同一个服务器上的两个Web应用程序所使用的Java类库可以实现相互隔离。这是最基本的要求，两个不同的应用程序可能会依赖同一个第三方类库的不同版本，不能要求一个
类库在一个服务器中只有一份，服务器应当保证两个应用程序的类库可以互相使用

2、部署在同一个服务器上的两个Web应用程序所使用的Java类库可以相互共享。这个需求也很常见，比如相同的Spring类库10个应用程序在用不可能分别存放在各个应用程序的隔离目录中

3、支持热替换，我们知道JSP文件最终要编译成.class文件才能由虚拟机执行，但JSP文件由于其纯文本存储特性，运行时修改的概率远远大于第三方类库或自身.class文件，而且JSP这种
网页应用也把修改后无须重启作为一个很大的优势看待

由于存在上述问题，因此Java提供给用户使用的ClassLoader就无法满足需求了。Tomcat服务器就有自己的ClassLoader架构，当然，还是以双亲委派模型为基础的：*/


/*从java.lang.ClassLoader的loadClass(String name, boolean resolve)方法的解析来看，可以得出以下2个结论：

1、如果不想打破双亲委派模型，那么只需要重写findClass方法即可

2、如果想打破双亲委派模型，那么就重写整个loadClass方法*/

//自定义一个类加载器，里面主要是一些IO和NIO的内容，另外注意一下defineClass方法可以把二进制流字节组成的文件转换为一
// 个java.lang.Class----只要二进制字节流的内容符合Class文件规范。我们自定义的MyClassLoader继承自java.lang.ClassLoader，
// 只实现findClass方法
public class TestMyClassLoader extends ClassLoader{
    public TestMyClassLoader() {

    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File file = getClassFile(name);

        try {
            byte[] bytes = getClassByte(file);
            Class<?> c = this.defineClass(name,bytes,0,bytes.length);
            return c;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return  super.findClass(name);
    }

    public TestMyClassLoader(ClassLoader parent){
        super(parent);
    }

    private File getClassFile(String name){
        File file = new File("F:\\Test.class");
        return  file;
    }

    //得到class字节流
    private byte[] getClassByte(File file) throws IOException {
        FileInputStream fs = new FileInputStream(file);
        FileChannel fl= fs.getChannel();

        ByteArrayOutputStream bous = new ByteArrayOutputStream();
        WritableByteChannel wbc = Channels.newChannel(bous);

        ByteBuffer by = ByteBuffer.allocate(1024);

        while (true){
            int i = fl.read(by);
            if (i==0|| i ==-1) break;

            by.flip();

            wbc.write(by);

            by.clear();
        }

        fs.close();

        return bous.toByteArray();

    }


    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        TestMyClassLoader myClassLoader=new TestMyClassLoader();

       // Class<?> c = Class.forName("Test",true,myClassLoader);
        Class<?> c = Class.forName("java.lang.Object");

        Object obj = c.newInstance();

        System.out.println(obj);
        System.out.println(obj.getClass().getClassLoader());
    }
}
